package com.fa4j.third.base.proxy;

import com.fa4j.common.base.exception.CommonServerError;
import com.fa4j.common.base.exception.ServerException;
import com.fa4j.third.base.adapter.AbstractThirdServiceAdapter;
import com.fa4j.third.base.model.AbstractBizCmd;
import com.fa4j.third.base.model.ThirdBizProps;
import com.fa4j.third.base.service.ThirdConfigService;
import jakarta.validation.ConstraintViolation;
import jakarta.validation.ConstraintViolationException;
import jakarta.validation.Valid;
import jakarta.validation.Validator;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import lombok.Synchronized;
import lombok.extern.slf4j.Slf4j;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.NestedExceptionUtils;
import org.springframework.util.ReflectionUtils;
import org.springframework.util.StringUtils;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;

@Slf4j
@RequiredArgsConstructor
public class ThirdInvocationHandler<T> implements InvocationHandler {
    private final ThirdConfigService thirdConfigService;
    private final List<Class<T>> adapterClassList;
    private final Map<String, T> adapterMap = new ConcurrentHashMap<>();
    private final Map<T, AtomicInteger> adapterCountMap = new ConcurrentHashMap<>();

    private final Validator validator;

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        valid(method, args);
        String bizCode = getBizCode(method, args);
        ThirdBizProps bizProps = getBizProps(bizCode);
        T adapter = getAdapter(bizProps);
        Method adapterMethod = getAdapterMethod(adapter, method);
        Logger logger = LoggerFactory.getLogger(adapterMethod.getDeclaringClass());
        long start = System.currentTimeMillis();
        try {
            logger.info("调用[{}]参数:{}", method.getName(), args);
            adapterCountMap.get(adapter).getAndIncrement();
            Object result = adapterMethod.invoke(adapter, args);
            long end = System.currentTimeMillis();
            logger.info("调用[{}]耗时:{}ms,结果:{}", method.getName(), (end - start), result);
            return result;
        } catch (Exception e) {
            Throwable throwable = NestedExceptionUtils.getMostSpecificCause(e);
            long end = System.currentTimeMillis();
            logger.error("调用[{}]异常,耗时:{}ms", method.getName(), (end - start), throwable);
            throw throwable;
        } finally {
            adapterCountMap.get(adapter).getAndDecrement();
        }
    }

    private void valid(Method method, Object[] args) {
        if (args == null) {
            return;
        }
        for (int i = 0; i < method.getParameters().length; i++) {
            Parameter parameter = method.getParameters()[i];
            if (parameter.getAnnotation(Valid.class) != null) {
                Object arg = args[i];
                if (arg != null) {
                    Set<ConstraintViolation<Object>> violations = validator.validate(arg);
                    if (!violations.isEmpty()) {
                        throw new ConstraintViolationException(violations);
                    }
                }
            }
        }
    }

    private Method getAdapterMethod(T adapter, Method method) {
        return ReflectionUtils.findMethod(adapter.getClass(), method.getName(), method.getParameterTypes());
    }

    private T getAdapter(ThirdBizProps bizProps) {
        T instance = null;
        if (adapterMap.containsKey(bizProps.getBizCode())) {
            instance = adapterMap.get(bizProps.getBizCode());
            if (instance instanceof AbstractThirdServiceAdapter adapter) {
                if (!bizProps.equals(adapter.getProps())) {
                    log.info("刷新发送变化:{}", bizProps);
                    closeAdapter(adapter);
                    instance = null;
                }
            }
        }
        if (instance == null) {
            for (Class<T> clazz : adapterClassList) {
                if (clazz.getSimpleName().startsWith(
                        StringUtils.capitalize(bizProps.getAdapterName()))) {
                    instance = newInstance(clazz, bizProps);
                    return instance;
                }
            }
        }
        if (instance == null) {
            throw CommonServerError.NOT_IMPLEMENTED.build("适配器" + bizProps.getAdapterName());
        }
        return instance;
    }

    private void closeAdapter(AbstractThirdServiceAdapter adapter) {
        AtomicInteger count = adapterCountMap.get(adapter);
        if (count == null) {
            return;
        }
        CompletableFuture.runAsync(() -> {
            try {
                while (count.get() > 0) {
                    TimeUnit.SECONDS.sleep(1);
                }
                adapter.close();
                adapterCountMap.remove(adapter);
                log.info("关闭适配器{}[{}]", adapter.getProps().getBizCode(), adapter.getProps().getAdapterParams());
            } catch (Exception e) {
                log.error("关闭{}[{}]异常", adapter.getProps().getBizCode(), adapter.getProps().getAdapterParams(), e);
            }
        });
    }

    @Synchronized
    @SneakyThrows
    private T newInstance(Class<T> adapterClass, ThirdBizProps bizProps) {
        T instance = adapterClass.getConstructor().newInstance();
        if (instance instanceof AbstractThirdServiceAdapter adapter) {
            adapter.setProps(bizProps);
            adapter.init();
        }
        adapterMap.put(bizProps.getBizCode(), instance);
        adapterCountMap.put(instance, new AtomicInteger(0));
        return instance;
    }

    private ThirdBizProps getBizProps(String bizCode) {
        return thirdConfigService.getBizProps(bizCode)
                .orElseThrow(() -> CommonServerError.NOT_SETTING.build(bizCode));
    }

    private String getBizCode(Method method, Object[] args) {
        if (args == null) {
            throw new ServerException("方法参数不能为空");
        }
        String bizCode = null;
        for (int i = 0; i < method.getParameters().length; i++) {
            Parameter parameter = method.getParameters()[i];
            Object arg = args[i];
            if (arg instanceof AbstractBizCmd cmd) {
                bizCode = cmd.getBizCode();
                break;
            } else if ("bizCode".equals(parameter.getName()) && arg instanceof String) {
                bizCode = (String) arg;
                break;
            }
        }
        if (bizCode == null) {
            throw new ServerException("未找到bizCode参数");
        }
        return bizCode;

    }
}
